package ru.papergames.battleserver.model.battle.services;

import ru.papergames.Config;
import ru.papergames.battleserver.model.battle.BallColor;
import ru.papergames.battleserver.model.battle.GameBall;
import ru.papergames.battleserver.model.battle.GameBallsLine;
import ru.papergames.battleserver.model.battle.map.BattleMap;
import ru.papergames.battleserver.model.battle.map.Cell;
import ru.papergames.battleserver.model.battle.map.LinesToBeRemovedInfo;
import ru.papergames.battleserver.model.battle.npc.LineVector;

import java.util.*;

/**
 * @author Alexander Troshanin
 */
public class AllLinesFinderImpl implements LinesFinder {

    private final static int VECTOR_1 = 1;
    private final static int VECTOR_2 = 2;
    private final static int VECTOR_3 = 4;
    private final static int VECTOR_4 = 8;

    private static AllLinesFinderImpl instance = new AllLinesFinderImpl();

    public static AllLinesFinderImpl getInstance() {
        return instance;
    }

    private AllLinesFinderImpl() {
    }

    @Override
    public LinesToBeRemovedInfo findCellsToBeFree(BattleMap map, BallColor color) {
        Set<GameBall> playerBalls = map.getBallsOfColor(color);
        if (playerBalls.size() < Config.LINE_LENGTH) {
            return LinesToBeRemovedInfo.STATIC_EMPTY;
        }
        List<Integer> linesSizes = new ArrayList<Integer>();
        List<GameBallsLine> lines = new ArrayList<GameBallsLine>();
        Map<Cell, CellUsedInLines> cellsToBeFree = new HashMap<Cell, CellUsedInLines>();
        for (GameBall ball : playerBalls) {
            CellUsedInLines cellUsedInLines = cellsToBeFree.get(ball.getCell());
            for (LineVector vector : LineVector.values()) {
                if (cellUsedInLines == null || !cellUsedInLines.isUsedInLineWithVector(vector.getBitMask())) {
                    List<GameBall> line = checkLine(map, ball, vector, cellsToBeFree, linesSizes);
                    if (line != null) {
                        lines.add(new GameBallsLine(vector, line));
                    }
                }
            }
            /*if (cellUsedInLines == null || !cellUsedInLines.isUsedInLineWithVector(VECTOR_1)) {
                checkLine0(map, ball, 0, 1, VECTOR_1, cellsToBeFree, linesSizes); // vertical
            }
            if (cellUsedInLines == null || !cellUsedInLines.isUsedInLineWithVector(VECTOR_2)) {
                checkLine0(map, ball, 1, 0, VECTOR_2, cellsToBeFree, linesSizes); // horizontal
            }
            if (cellUsedInLines == null || !cellUsedInLines.isUsedInLineWithVector(VECTOR_3)) {
                checkLine0(map, ball, 1, 1, VECTOR_3, cellsToBeFree, linesSizes); // diagonal +y
            }
            if (cellUsedInLines == null || !cellUsedInLines.isUsedInLineWithVector(VECTOR_4)) {
                checkLine0(map, ball, 1, -1, VECTOR_4, cellsToBeFree, linesSizes); // diagonal -y
            }*/

        }
        return new LinesToBeRemovedInfo(cellsToBeFree.keySet(), linesSizes, lines);
    }

    private List<GameBall> checkLine(BattleMap map, GameBall ball, LineVector vector, Map<Cell, CellUsedInLines> cellsToBeFree, List<Integer> linesSizes) {
        int dx = vector.getDx();
        int dy = vector.getDy();
        Cell cell = ball.getCell();
        BallColor ballColor = ball.getColor();
        int cellX = cell.getX();
        int cellY = cell.getY();
        int t = 0;
        List<GameBall> line = new ArrayList<GameBall>();
        line.add(ball);
        boolean v1 = true;
        boolean v2 = true;
        while(true) {
            t++;
            GameBall b1 = v1 ? map.getGameBall(cellX + t * dx, cellY + t * dy) : null;
            GameBall b2 = v2 ? map.getGameBall(cellX - t * dx, cellY - t * dy) : null;
            BallColor c1 = b1 != null ? b1.getColor() : null;
            BallColor c2 = b2 != null ? b2.getColor() : null;
            if (ballColor != c1 && ballColor != c2) {
                break;
            }
            if (ballColor == c1) {
                line.add(b1);
            } else {
                v1 = false;
            }
            if (ballColor == c2) {
                line.add(b2);
            } else {
                v2 = false;
            }
        }
        if (line.size() >= Config.LINE_LENGTH) {
            linesSizes.add(line.size());
            for (GameBall b : line) {
                CellUsedInLines cellUsedInLines = cellsToBeFree.get(b.getCell());
                if (cellUsedInLines == null) {
                    cellUsedInLines = new CellUsedInLines(b.getCell());
                    cellsToBeFree.put(b.getCell(), cellUsedInLines);
                }
                cellUsedInLines.setUsedInLineVector(vector.getBitMask());
            }
            return line;
        }
/*
        if (line.size() >= Config.LINE_LENGTH) {
            List<Cell> lst = new ArrayList<Cell>();
            linesSizes.add(lst);
            for (GameBall b : line) {
                CellUsedInLines cellUsedInLines = cellsToBeFree.get(b.getCell());
                if (cellUsedInLines == null) {
                    cellUsedInLines = new CellUsedInLines(b.getCell());
                    cellsToBeFree.put(b.getCell(), cellUsedInLines);
                }
                cellUsedInLines.setUsedInLineVector(vector);
                lst.add(b.getCell());
            }
            return line;
        }
*/
        return null;
    }

    private List<GameBall> checkLine0(BattleMap map, GameBall ball, int dx, int dy,
                                     int vector, Map<Cell, CellUsedInLines> cellsToBeFree, List<Integer> linesSizes) {
        Cell cell = ball.getCell();
        BallColor ballColor = ball.getColor();
        int cellX = cell.getX();
        int cellY = cell.getY();
        int t = 0;
        List<GameBall> line = new ArrayList<GameBall>();
        line.add(ball);
        boolean v1 = true;
        boolean v2 = true;
        while(true) {
            t++;
            GameBall b1 = v1 ? map.getGameBall(cellX + t * dx, cellY + t * dy) : null;
            GameBall b2 = v2 ? map.getGameBall(cellX - t * dx, cellY - t * dy) : null;
            BallColor c1 = b1 != null ? b1.getColor() : null;
            BallColor c2 = b2 != null ? b2.getColor() : null;
            if (ballColor != c1 && ballColor != c2) {
                break;
            }
            if (ballColor == c1) {
                line.add(b1);
            } else {
                v1 = false;
            }
            if (ballColor == c2) {
                line.add(b2);
            } else {
                v2 = false;
            }
        }
        if (line.size() >= Config.LINE_LENGTH) {
            linesSizes.add(line.size());
            for (GameBall b : line) {
                CellUsedInLines cellUsedInLines = cellsToBeFree.get(b.getCell());
                if (cellUsedInLines == null) {
                    cellUsedInLines = new CellUsedInLines(b.getCell());
                    cellsToBeFree.put(b.getCell(), cellUsedInLines);
                }
                cellUsedInLines.setUsedInLineVector(vector);
            }
            return line;
        }
/*
        if (line.size() >= Config.LINE_LENGTH) {
            List<Cell> lst = new ArrayList<Cell>();
            linesSizes.add(lst);
            for (GameBall b : line) {
                CellUsedInLines cellUsedInLines = cellsToBeFree.get(b.getCell());
                if (cellUsedInLines == null) {
                    cellUsedInLines = new CellUsedInLines(b.getCell());
                    cellsToBeFree.put(b.getCell(), cellUsedInLines);
                }
                cellUsedInLines.setUsedInLineVector(vector);
                lst.add(b.getCell());
            }
            return line;
        }
*/
        return null;
    }

    private class CellUsedInLines {
        private Cell cell;
        private int linesVectorsBitmap;

        private CellUsedInLines(Cell cell) {
            this.cell = cell;
        }

        public boolean isUsedInLineWithVector(int vector) {
            return (linesVectorsBitmap & vector) > 0;
        }

        public void setUsedInLineVector(int vector) {
            linesVectorsBitmap |= vector;
        }

        public Cell getCell() {
            return cell;
        }
    }
}
